/**
 * Copyright (c) 2009 Collaborative Development Group, C.S. Dept., University of Bari
 *
 * All rights reserved. This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0  which accompanies this distribution, and is 
 * available at http://www.eclipse.org/legal/epl-v10.html
 */
package it.uniba.di.cdg.penelope.ui.mvp.databinding;

import static it.uniba.di.cdg.penelope.util.ReflectionUtilities.getUnproxiedType;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;

/**
 * Implementation of the {@link IDataBindingService}.
 * TODO Provide support for the Update Strategy 
 * FIXME We are using the JavaBeans API directly: what about using Eclipse Databinding API event 
 *        between the model and the presenter? 
 */
public class EclipseDataBindingService implements IDataBindingService {

	/* (non-Javadoc)
	 * @see it.uniba.di.cdg.penelope.ui.IDataBindingService#bind(org.eclipse.ui.IViewPart, java.lang.Object)
	 */
	@Override
	public void bind( Object view, Object presentationModel ) {
		DataBindingContext bindingContext = new DataBindingContext();

		IObservableValue uiElement;
		IObservableValue modelElement;

		Class<?> viewClass = getUnproxiedType( view.getClass() );
		Field[] fields = viewClass.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			final Field field = fields[i];
			boolean isAccessibleField = field.isAccessible();
			try {
				System.out.println( "Scanning field: " + field );
				
				if (field.isAnnotationPresent( DataBind.class )) {
					field.setAccessible( true );
					
					DataBind ann = field.getAnnotation( DataBind.class );
					String propertyName = ann.value().isEmpty() ? field.getName() : ann.value();

					uiElement = createUIElement( field, view, ann );
					if (uiElement == null) {
						throw new DataBindingException( String.format( "UI Element is null (%s)", field.getName()  ) );
					}
					modelElement = BeansObservables.observeValue( presentationModel, propertyName );
					if (modelElement == null) {
						throw new DataBindingException( String.format( "Model Element is null (%s)", propertyName ) );
					}
					
					UpdateValueStrategy strategy = createValidationStrategy( (Control) field.get( view ), field.getAnnotation( Validator.class ) );
					bindingContext.bindValue( uiElement, modelElement, strategy, null );
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} finally {
				field.setAccessible( isAccessibleField );
			}
		}
	}

	/**
	 * @param ann
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private UpdateValueStrategy createValidationStrategy( Control control, Validator ann ) {
		UpdateValueStrategy strategy = null;
		if (ann == null || ann.validator() == IValidator.class ) {
			return null;
		}
		try {
			ControlDecoration controlDecoration = new ControlDecoration( control, SWT.LEFT | SWT.TOP );
			controlDecoration.setDescriptionText( ann.message() );
			FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault().getFieldDecoration(
					FieldDecorationRegistry.DEC_ERROR );
			controlDecoration.setImage( fieldDecoration.getImage() );			

			Constructor<?> ctor = ann.validator().getConstructor( ControlDecoration.class, String.class );
			IValidator validator = (IValidator) ctor.newInstance( controlDecoration, ann.message() );

			strategy = new UpdateValueStrategy().setAfterConvertValidator( validator );
		} catch (Throwable t) {
			throw new DataBindingException( t.getCause() );
		}
		return strategy;
	}

	private IObservableValue createUIElement( Field field, Object view, DataBind ann ) {
		IObservableValue uiElement = null;
		boolean isAccessible = field.isAccessible();
		try {
			field.setAccessible( true );
			Object fieldValue = field.get( view );
			if (fieldValue instanceof Text) {
				uiElement = SWTObservables.observeText( (Control) fieldValue, ann.swtFlags() );
				
			} else if (fieldValue instanceof Button) {
				uiElement = SWTObservables.observeSelection( (Control) fieldValue );
			}
		} catch (Throwable e) {
			throw new DataBindingException( e );
		} finally {
			field.setAccessible( isAccessible );
		}
		return uiElement;
	}
}
